<!DOCTYPE html>
<html lang="en">
<head>
  <title>Multi-Process Development Model Enhancement - Born to build better enterprise frameworks and apps</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/en/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/advanced/cluster-client.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/advanced/cluster-client.html" >中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>Multi-Process Development Model Enhancement</h1>
    <p>In the previous <a href="../core/cluster-and-ipc.html">Multi-Process Model chapter</a>, we covered the multi-process model of the framework in detail, whose Agent process suits for a common class of scenarios: some middleware clients need to establish a persistent connection with server. In theory, a server had better establish only one persistent connection. However, the multi-process model will result in n times (n = number of Worker processes) connections being created.</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">+--------+   +--------+</span><br><span class="line">| Client |   | Client |   ... n</span><br><span class="line">+--------+   +--------+</span><br><span class="line">    |  \     /   |</span><br><span class="line">    |    \ /     |        n * m links</span><br><span class="line">    |    / \     |</span><br><span class="line">    |  /     \   |</span><br><span class="line">+--------+   +--------+</span><br><span class="line">| Server |   | Server |   ... m</span><br><span class="line">+--------+   +--------+</span><br></pre></td></tr></table></figure>
<p>In order to reuse persistent connections as much as possible (because they are very valuable resources for server), we put it into the Agent process to maintain, and then we transmit data to each Worker via messenger. It's feasible but often we need to write a lot of code to encapsulate the interface and to realize data transmission, which is very troublesome.</p>
<p>In addition, it's relatively inefficient to transmit data via messenger, since messenger will do the transmission through the Master; In case IPC channel goes wrong, it would probably break Master process down.</p>
<p>Then is there any better way? The answer is yes. We provide a new type of model to reduce the complexity of this type of client encapsulation. The new Model bypasses the Master by establishing a direct socket between Agent and Worker. And as an external interface, Agent maintains shared connection among multiple Worker processes.</p>
<h2 id="core-idea"><a class="markdown-anchor" href="#core-idea">#</a> core idea</h2>
<ul>
<li>Inspired by the <a href="http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf" target="_blank" rel="noopener">Leader/Follower</a> model</li>
<li>The client is divided into two roles:
  - Leader: responsible for maintaining the connection with the remote server, only one Leader for the same type of client.
  - Follower: delegates specific operations to the Leader. A common way is Subscribe-Model (let the Leader interact with remote server and wait for its return).</li>
<li>How to determine who Leader is, who Follower is? There are two modes:
  - Free competition mode: clients determine the Leader by the competition of the local port when start up. For example: every one tries to monitor port 7777, and finally the only one instance who seizes it will become Leader, the rest will be Followers.
  - Mandatory mode: the framework designates a Leader and the rest are Followers.</li>
<li>we use mandatory mode inside the framework. The Leader can only be created inside the Agent, which is also in line with our positioning of the Agent.</li>
<li>When the framework starts up, Master will randomly select an available port as the communication port monitored by the Cluster Client, and passes it by parameter to Agent and App Worker.</li>
<li>Leader communicates with Follower through direct socket connection (through communication port), no longer needs Master to transit.</li>
</ul>
<p>Under the new mode, the client's communication is as follows:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">             +-------+</span><br><span class="line">             | start |</span><br><span class="line">             +---+---+</span><br><span class="line">                 |</span><br><span class="line">        +--------+---------+</span><br><span class="line">      __| port competition |__</span><br><span class="line">win /   +------------------+  \ lose</span><br><span class="line">   /                           \</span><br><span class="line">+---------------+     tcp conn     +-------------------+</span><br><span class="line">| Leader(Agent) |<span class="xml"><span class="tag">&lt;<span class="name">----------------</span>&gt;</span>| Follower(Worker1) |</span></span><br><span class="line"><span class="xml">+---------------+                  +-------------------+</span></span><br><span class="line"><span class="xml">    |            \ tcp conn</span></span><br><span class="line"><span class="xml">    |             \</span></span><br><span class="line"><span class="xml">+--------+         +-------------------+</span></span><br><span class="line"><span class="xml">| Client |         | Follower(Worker2) |</span></span><br><span class="line"><span class="xml">+--------+         +-------------------+</span></span><br></pre></td></tr></table></figure>
<h2 id="client-interface-type-abstraction"><a class="markdown-anchor" href="#client-interface-type-abstraction">#</a> Client interface type abstraction</h2>
<p>We abstract the client interface into the following two broad categories, which is also a specification of the client interface. For clients that are in line with norms, we can automatically wrap it as Leader / Follower mode.</p>
<ul>
<li>Subscribe / Publish Mode
  - The <code>subscribe(info, listener)</code> interface contains two parameters. The first one is the information subscribed and the second one is callback function for subscribe.
  - The <code>publish(info)</code> interface contains a parameter which is the information subscribed.</li>
<li>Invoke Mode, supports three styles of interface: callback, promise and generator function, but generator function is recommended.</li>
</ul>
<p>Client example</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> Base = <span class="built_in">require</span>(<span class="string">'sdk-base'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Client</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(options) &#123;</span><br><span class="line">    <span class="keyword">super</span>(options);</span><br><span class="line">    <span class="comment">// remember to invoke ready after initialization is successful</span></span><br><span class="line">    <span class="keyword">this</span>.ready(<span class="literal">true</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Subscribe</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @param &#123;Object&#125; info - subscription information (a JSON object, try not to include attributes such as Function, Buffer, Date)</span></span><br><span class="line"><span class="comment">   * @param &#123;Function&#125; listener - monitoring callback function, receives a parameter as the result of monitoring</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  subscribe(info, listener) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Publish</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @param &#123;Object&#125; info - publishing information, which is similar to that of subscribe described above</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  publish(info) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Get data (invoke)</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * @param &#123;String&#125; id - id</span></span><br><span class="line"><span class="comment">   * @return &#123;Object&#125; result</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">async</span> getData(id) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="exception-handling"><a class="markdown-anchor" href="#exception-handling">#</a> exception handling</h2>
<ul>
<li>If Leader &quot;dies&quot;, a new round of port contention will be triggered. The instance which seizes the port will be elected as the new Leader.</li>
<li>To ensure that the channel between Leader and Follower is healthy, heartbeat mechanism needs to be introduced. If the Follower does not send a heartbeat packet within a fixed time, the Leader will proactively disconnect from the Follower, which will trigger the reinitialization of Follower.</li>
</ul>
<h2 id="protocol-and-time-series-to-invoke"><a class="markdown-anchor" href="#protocol-and-time-series-to-invoke">#</a> protocol and time series to invoke</h2>
<p>Leader and Follower exchange data via the following protocols:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">0</span>       <span class="number">1</span>       <span class="number">2</span>               <span class="number">4</span>                                                              <span class="number">12</span></span><br><span class="line">+-------+-------+---------------+---------------------------------------------------------------+</span><br><span class="line">|version|req/res|    reserved   |                          request id                           |</span><br><span class="line">+-------------------------------+-------------------------------+-------------------------------+</span><br><span class="line">|           timeout             |   connection object length    |   application object length   |</span><br><span class="line">+-------------------------------+---------------------------------------------------------------+</span><br><span class="line">|         conn object (<span class="built_in">JSON</span> format)  ...                    |            app object             |</span><br><span class="line">+-----------------------------------------------------------+                                   |</span><br><span class="line">|                                          ...                                                  |</span><br><span class="line">+-----------------------------------------------------------------------------------------------+</span><br></pre></td></tr></table></figure>
<ol>
<li>On the communication port Leader starts a Local Server, via which all Leaders / Followers communicate.</li>
<li>After Follower connects Local Server, it will firstly send a register channel packet (introduction of the channel concept is to distinguish between different types of clients)</li>
<li>Local Server will assign Follower to a specified Leader (match based on client type).</li>
<li>Follower sends requests to Leader to subscribe and publish.</li>
<li>Leader notifies Follower through the subscribe result packet when the subscription data changes.</li>
<li>Follower sends a call request to the Leader. The Leader executes a corresponding operation after receiving, and returns the result.</li>
</ol>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">+----------+             +---------------+          +---------+</span><br><span class="line">| Follower |             |  Local Server |          |  Leader |</span><br><span class="line">+----------+             +---------------+          +---------+</span><br><span class="line">     |     register channel     |       assign to        |</span><br><span class="line">     + -----------------------&gt; |  --------------------&gt; |</span><br><span class="line">     |                          |                        |</span><br><span class="line">     |                                subscribe          |</span><br><span class="line">     + ------------------------------------------------&gt; |</span><br><span class="line">     |                                 publish           |</span><br><span class="line">     + ------------------------------------------------&gt; |</span><br><span class="line">     |                                                   |</span><br><span class="line">     |       subscribe result                            |</span><br><span class="line">     | <span class="xml"><span class="tag">&lt;<span class="name">------------------------------------------------</span> +</span></span></span><br><span class="line"><span class="xml">     |                                                   |</span></span><br><span class="line"><span class="xml">     |                                 invoke            |</span></span><br><span class="line"><span class="xml">     + ------------------------------------------------&gt; |</span></span><br><span class="line"><span class="xml">     |          invoke result                            |</span></span><br><span class="line"><span class="xml">     | <span class="tag">&lt;<span class="name">------------------------------------------------</span> +</span></span></span><br><span class="line"><span class="xml">     |                                                   |</span></span><br></pre></td></tr></table></figure>
<h2 id="specific-use"><a class="markdown-anchor" href="#specific-use">#</a> specific use</h2>
<p>In the following I will use a simple example to introduce how to make a client support Leader / Follower mode in the framework.</p>
<ul>
<li>The first step, our client is best to meet the interface conventions mentioned above, for example:</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// registry_client.js</span></span><br><span class="line"><span class="keyword">const</span> URL = <span class="built_in">require</span>(<span class="string">'url'</span>);</span><br><span class="line"><span class="keyword">const</span> Base = <span class="built_in">require</span>(<span class="string">'sdk-base'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RegistryClient</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(options) &#123;</span><br><span class="line">    <span class="keyword">super</span>(&#123;</span><br><span class="line">      <span class="comment">// Specify a method for asynchronous start </span></span><br><span class="line">      initMethod: <span class="string">'init'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">this</span>._options = options;</span><br><span class="line">    <span class="keyword">this</span>._registered = <span class="keyword">new</span> <span class="built_in">Map</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Start logic</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">async</span> init() &#123;</span><br><span class="line">    <span class="keyword">this</span>.ready(<span class="literal">true</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Get configuration</span></span><br><span class="line"><span class="comment">   * @param &#123;String&#125; dataId - the dataId</span></span><br><span class="line"><span class="comment">   * @return &#123;Object&#125; configuration</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">async</span> getConfig(dataId) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>._registered.get(dataId);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Subscribe</span></span><br><span class="line"><span class="comment">   * @param &#123;Object&#125; reg</span></span><br><span class="line"><span class="comment">   *   - &#123;String&#125; dataId - the dataId</span></span><br><span class="line"><span class="comment">   * @param &#123;Function&#125; listener - the listener</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  subscribe(reg, listener) &#123;</span><br><span class="line">    <span class="keyword">const</span> key = reg.dataId;</span><br><span class="line">    <span class="keyword">this</span>.on(key, listener);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">this</span>._registered.get(key);</span><br><span class="line">    <span class="keyword">if</span> (data) &#123;</span><br><span class="line">      process.nextTick(<span class="function"><span class="params">()</span> =&gt;</span> listener(data));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * publish</span></span><br><span class="line"><span class="comment">   * @param &#123;Object&#125; reg</span></span><br><span class="line"><span class="comment">   *   - &#123;String&#125; dataId - the dataId</span></span><br><span class="line"><span class="comment">   *   - &#123;String&#125; publishData - the publish data</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  publish(reg) &#123;</span><br><span class="line">    <span class="keyword">const</span> key = reg.dataId;</span><br><span class="line">    <span class="keyword">let</span> changed = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">this</span>._registered.has(key)) &#123;</span><br><span class="line">      <span class="keyword">const</span> arr = <span class="keyword">this</span>._registered.get(key);</span><br><span class="line">      <span class="keyword">if</span> (arr.indexOf(reg.publishData) === <span class="number">-1</span>) &#123;</span><br><span class="line">        changed = <span class="literal">true</span>;</span><br><span class="line">        arr.push(reg.publishData);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      changed = <span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">this</span>._registered.set(key, [reg.publishData]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (changed) &#123;</span><br><span class="line">      <span class="keyword">this</span>.emit(key, <span class="keyword">this</span>._registered.get(key).map(<span class="function"><span class="params">url</span> =&gt;</span> URL.parse (url, <span class="literal">true</span>)));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = RegistryClient;</span><br></pre></td></tr></table></figure>
<ul>
<li>The second step is to encapsulate the RegistryClient using the <code>agent.cluster</code> interface.</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// agent.js</span></span><br><span class="line"><span class="keyword">const</span> RegistryClient = <span class="built_in">require</span>(<span class="string">'registry_client'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">agent</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// encapsulate and instantiate RegistryClient</span></span><br><span class="line">  agent.registryClient = agent.cluster(RegistryClient)</span><br><span class="line">    <span class="comment">// parameter of create method is the parameter of RegistryClient constructor</span></span><br><span class="line">    .create(&#123;&#125;);</span><br><span class="line"></span><br><span class="line">  agent.beforeStart(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> agent.registryClient.ready();</span><br><span class="line">    agent.coreLogger.info(<span class="string">'registry client is ready'</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<ul>
<li>The third step, use the <code>app.cluster</code> interface to encapsulate RegistryClient</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="keyword">const</span> RegistryClient = <span class="built_in">require</span>(<span class="string">'registry_client'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.registryClient = app.cluster(RegistryClient).create(&#123;&#125;);</span><br><span class="line">  app.beforeStart(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> app.registryClient.ready();</span><br><span class="line">    app.coreLogger.info(<span class="string">'registry client is ready'</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// invoke subscribe to subscribe</span></span><br><span class="line">    app.registryClient.subscribe(&#123;</span><br><span class="line">      dataId: <span class="string">'demo.DemoService'</span>,</span><br><span class="line">    &#125;, val =&gt; &#123;</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// invoke publish to publsih data</span></span><br><span class="line">    app.registryClient.publish(&#123;</span><br><span class="line">      dataId: <span class="string">'demo.DemoService'</span>,</span><br><span class="line">      publishData: <span class="string">'xxx'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// invoke getConfig interface</span></span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> app.registryClient.getConfig(<span class="string">'demo.DemoService'</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(res);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Is not it simple?</p>
<p>Of course, if your client is not so 『standard』, then you may need to use some other APIs, for example, your subscription function is not called subscribe, but sub.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MockClient</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(options) &#123;</span><br><span class="line">    <span class="keyword">super</span>(&#123;</span><br><span class="line">      initMethod: <span class="string">'init'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">this</span>._options = options;</span><br><span class="line">    <span class="keyword">this</span>._registered = <span class="keyword">new</span> <span class="built_in">Map</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">async</span> init() &#123;</span><br><span class="line">    <span class="keyword">this</span>.ready(<span class="literal">true</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  sub(info, listener) &#123;</span><br><span class="line">    <span class="keyword">const</span> key = reg.dataId;</span><br><span class="line">    <span class="keyword">this</span>.on(key, listener);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">this</span>._registered.get(key);</span><br><span class="line">    <span class="keyword">if</span> (data) &#123;</span><br><span class="line">      process.nextTick(<span class="function"><span class="params">()</span> =&gt;</span> listener(data));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>You need to set it manually with the delegate API</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// agent.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">agent</span> =&gt;</span> &#123;</span><br><span class="line">  agent.mockClient = agent.cluster(MockClient)</span><br><span class="line">    <span class="comment">// delegate sub to logic of subscribe</span></span><br><span class="line">    .delegate(<span class="string">'sub'</span>, <span class="string">'subscribe'</span>)</span><br><span class="line">    .create();</span><br><span class="line"></span><br><span class="line">  agent.beforeStart(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> agent.mockClient.ready();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.mockClient = app.cluster(MockClient)</span><br><span class="line">    <span class="comment">// delegate sub to subscribe logic</span></span><br><span class="line">    .delegate(<span class="string">'sub'</span>, <span class="string">'subscribe'</span>)</span><br><span class="line">    .create();</span><br><span class="line"></span><br><span class="line">  app.beforeStart(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> app.mockClient.ready();</span><br><span class="line"></span><br><span class="line">    app.sub(&#123; <span class="attr">id</span>: <span class="string">'test-id'</span> &#125;, val =&gt; &#123;</span><br><span class="line">      <span class="comment">// put your code here</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>We've already known that using cluster-client allows us to develop a 『pure』 RegistryClient without understanding the multi-process model. We can only focus on interacting with server, and use the cluster-client with a simple wrap to get a ClusterClient which supports multi-process model. The RegistryClient here is actually a DataClient that is specifically responsible for data communication with remote service.</p>
<p>You may have noticed that the ClusterClient brings with several constraints at the same time. If you want to expose the same approach to each process, RegistryClient can only support sub/pub mode and asynchronous API calls. Because all interactions in multi-process model must use socket communications, under which it is bound to bring this constraint.</p>
<p>Suppose we want to realize a synchronous get method. Put subscribed data directly into memory and use the get method to return data directly. How to achieve it? The real situation may be more complicated.</p>
<p>Here, we introduce an APIClient best practice. For modules that have requirements of synchronous API such as reading cached data, an APIClient is encapsulated base on RegistryClient to implement these APIs that are not related to interaction with the remote server. The APIClient instance is exposed to the user.</p>
<p>In APIClient internal implementation:</p>
<ul>
<li>To obtain data asynchronously, invoke RegistryClient's API base on ClusterClient.</li>
<li>Interfaces that are unrelated to server, such as synchronous call, are to be implemented in APIClient. Since ClusterClient's APIs have flushed multi-process differences, there is no need to concern about multi-process model when calls to RegistryClient during developing APIClient.</li>
</ul>
<p>For example, add a synchronous get method with buffer in the APIClient module:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// some-client/index.js</span></span><br><span class="line"><span class="keyword">const</span> cluster = <span class="built_in">require</span>(<span class="string">'cluster-client'</span>);</span><br><span class="line"><span class="keyword">const</span> RegistryClient = <span class="built_in">require</span>(<span class="string">'./registry_client'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">APIClient</span> <span class="keyword">extends</span> <span class="title">Base</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(options) &#123;</span><br><span class="line">    <span class="keyword">super</span>(options);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// options.cluster is used to pass app.cluster to Egg's plugin</span></span><br><span class="line">    <span class="keyword">this</span>._client = (options.cluster || cluster)(RegistryClient).create(options);</span><br><span class="line">    <span class="keyword">this</span>._client.ready(<span class="function"><span class="params">()</span> =&gt;</span> <span class="keyword">this</span>.ready(<span class="literal">true</span>));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>._cache = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// subMap:</span></span><br><span class="line">    <span class="comment">// &#123;</span></span><br><span class="line">    <span class="comment">//   foo: reg1,</span></span><br><span class="line">    <span class="comment">//   bar: reg2,</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line">    <span class="keyword">const</span> subMap = options.subMap;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> subMap) &#123;</span><br><span class="line">      <span class="keyword">this</span>.subscribe(subMap[key], value =&gt; &#123;</span><br><span class="line">        <span class="keyword">this</span>._cache[key] = value;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  subscribe(reg, listener) &#123;</span><br><span class="line">    <span class="keyword">this</span>._client.subscribe(reg, listener);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  publish(reg) &#123;</span><br><span class="line">    <span class="keyword">this</span>._client.publish(reg);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  get(key) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>._cache[key];</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// at last the module exposes this APIClient</span></span><br><span class="line"><span class="built_in">module</span>.exports = APIClient;</span><br></pre></td></tr></table></figure>
<p>Then we can use this module like this:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js || agent.js</span></span><br><span class="line"><span class="keyword">const</span> APIClient = <span class="built_in">require</span>(<span class="string">'some-client'</span>); <span class="comment">// the module above</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> config = app.config.apiClient;</span><br><span class="line">  app.apiClient = <span class="keyword">new</span> APIClient(<span class="built_in">Object</span>.assign(&#123;&#125;, config, &#123; <span class="attr">cluster</span>: app.cluster &#125;);</span><br><span class="line">  app.beforeStart(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">await</span> app.apiClient.ready();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// config.$&#123;env&#125;.js</span></span><br><span class="line">exports.apiClient = &#123;</span><br><span class="line">  subMap: &#123;</span><br><span class="line">    foo: &#123;</span><br><span class="line">      id: <span class="string">''</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// bar...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>To make it easy for you to encapsulate <code>APIClient</code>, we provide an<code>APIClientBase</code> base class in the <a href="https://www.npmjs.com/package/cluster-client" target="_blank" rel="noopener">cluster-client</a> module. Then <code>APIClient</code> above can be rewritten as:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> APIClientBase = <span class="built_in">require</span>(<span class="string">'cluster-client'</span>).APIClientBase;</span><br><span class="line"><span class="keyword">const</span> RegistryClient = <span class="built_in">require</span>(<span class="string">'./registry_client'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">APIClient</span> <span class="keyword">extends</span> <span class="title">APIClientBase</span> </span>&#123;</span><br><span class="line">  <span class="comment">// return the original client class</span></span><br><span class="line">  get DataClient() &#123;</span><br><span class="line">    <span class="keyword">return</span> RegistryClient;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// used to set the cluster-client related parameters, equivalent to the second parameter of the cluster method</span></span><br><span class="line">  get clusterOptions() &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      responseTimeout: <span class="number">120</span> * <span class="number">1000</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  subscribe(reg, listener) &#123;</span><br><span class="line">    <span class="keyword">this</span>._client.subscribe(reg, listener);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  publish(reg) &#123;</span><br><span class="line">    <span class="keyword">this</span>._client.publish(reg);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  get(key) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>._cache[key];</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>in conclusion:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">+------------------------------------------------+</span><br><span class="line">| APIClient                                      |</span><br><span class="line">|       +----------------------------------------|</span><br><span class="line">|       | ClusterClient                          |</span><br><span class="line">|       |      +---------------------------------|</span><br><span class="line">|       |      | RegistryClient                  |</span><br><span class="line">+------------------------------------------------+</span><br></pre></td></tr></table></figure>
<ul>
<li>RegistryClient - responsible for communicating with remote service, to access data, supports for asynchronous APIs only, and does't care about multi-process model.</li>
<li>ClusterClient - a client instance that is simply wrapped by the cluster-client module and is responsible for automatically flushing differences in multi-process model.</li>
<li>APIClient - internally calls ClusterClient to synchronize data, without the need to concern about multi-process model and is the final exposed module for users. APIs are exposed Through this, and support for synchronization and asynchronization.</li>
</ul>
<p>Students who are interested may have look at <a href="https://github.com/eggjs/egg/issues/322" target="_blank" rel="noopener">enhanced multi-process development model</a> discussion process.</p>
<h2 id="the-configuration-items-related-to-cluster-client-in-the-framework"><a class="markdown-anchor" href="#the-configuration-items-related-to-cluster-client-in-the-framework">#</a> the configuration items related to cluster-client in the framework</h2>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @property &#123;Number&#125; responseTimeout - response timeout, default is 60000</span></span><br><span class="line"><span class="comment"> * @property &#123;Transcode&#125; [transcode]</span></span><br><span class="line"><span class="comment"> *   - &#123;Function&#125; encode - custom serialize method</span></span><br><span class="line"><span class="comment"> *   - &#123;Function&#125; decode - custom deserialize method</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">config.clusterClient = &#123;</span><br><span class="line">  responseTimeout: <span class="number">60000</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<table>
<thead>
<tr>
<th>Configuration Items</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>responseTimeout</td>
<td>number</td>
<td>60000 (one minute)</td>
<td>Global interprocess communication timeout, you cannot set too short, because the proxy interface itself has a timeout setting</td>
</tr>
<tr>
<td>transcode</td>
<td>function</td>
<td>N/A</td>
<td>Serialization of interprocess communication, by default <a href="https://www.npmjs.com/package/serialize-json" target="_blank" rel="noopener">serialize-json</a> (set up manually is not recommended)</td>
</tr>
</tbody>
</table>
<p>The above is about global configuration. If you want to do a separate setting for a client:</p>
<ul>
<li>You can override by setting the second argument <code>options</code> in<code>app/agent.cluster(ClientClass, options)</code></li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">app.registryClient = app.cluster(RegistryClient, &#123;</span><br><span class="line">  responseTimeout: <span class="number">120</span> * <span class="number">1000</span>, <span class="comment">// the parameters passing here are related to cluster-client</span></span><br><span class="line">&#125;).create(&#123;</span><br><span class="line">  <span class="comment">// here are parameters required by RegistryClient</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<ul>
<li>You can also override the <code>getter</code> attribute of <code>clusterOptions</code> in <code>APIClientBase</code>.</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> APIClientBase = <span class="built_in">require</span>(<span class="string">'cluster-client'</span>).APIClientBase;</span><br><span class="line"><span class="keyword">const</span> RegistryClient = <span class="built_in">require</span>(<span class="string">'./registry_client'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">APIClient</span> <span class="keyword">extends</span> <span class="title">APIClientBase</span> </span>&#123;</span><br><span class="line">  get DataClient() &#123;</span><br><span class="line">    <span class="keyword">return</span> RegistryClient;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  get clusterOptions() &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      responseTimeout: <span class="number">120</span> * <span class="number">1000</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = APIClient;</span><br></pre></td></tr></table></figure>
  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/advanced/cluster-client.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/advanced/cluster-client.html" >中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">Guide<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/en/intro/index.html" class="menu-link">What is Egg?</a></li><li><a href="/en/intro/egg-and-koa.html" class="menu-link">Egg and Koa</a></li><li><a href="/en/intro/quickstart.html" class="menu-link">Quick Start</a></li><li><a href="/en/tutorials/progressive.html" class="menu-link">Progressive</a></li><li><a href="/en/migration.html" class="menu-link">Migration to 2.x</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">Basis Function<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/en/basics/structure.html" class="menu-link">Directory Structure</a></li><li><a href="/en/basics/objects.html" class="menu-link">Built-in Objects</a></li><li><a href="/en/basics/env.html" class="menu-link">Environment</a></li><li><a href="/en/basics/config.html" class="menu-link">Configuration</a></li><li><a href="/en/basics/middleware.html" class="menu-link">Middleware</a></li><li><a href="/en/basics/router.html" class="menu-link">Router</a></li><li><a href="/en/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/en/basics/service.html" class="menu-link">Service</a></li><li><a href="/en/basics/plugin.html" class="menu-link">Plugin</a></li><li><a href="/en/basics/schedule.html" class="menu-link">Schedule</a></li><li><a href="/en/basics/extend.html" class="menu-link">Extend</a></li><li><a href="/en/basics/app-start.html" class="menu-link">Custom Init</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">Core<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/en/core/development.html" class="menu-link">Development</a></li><li><a href="/en/core/unittest.html" class="menu-link">Unit Testing</a></li><li><a href="/en/core/deployment.html" class="menu-link">Deployment</a></li><li><a href="/en/core/logger.html" class="menu-link">Logger</a></li><li><a href="/en/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/en/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/en/core/cluster-and-ipc.html" class="menu-link">Cluster and IPC</a></li><li><a href="/en/core/view.html" class="menu-link">View</a></li><li><a href="/en/core/error-handling.html" class="menu-link">Error Handling</a></li><li><a href="/en/core/security.html" class="menu-link">Security</a></li><li><a href="/en/core/i18n.html" class="menu-link">i18n</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">Tutorials<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/en/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/en/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/en/tutorials/passport.html" class="menu-link">Passport</a></li><li><a href="/en/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/en/tutorials/assets.html" class="menu-link">Assets</a></li><li><a href="/en/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">Advanced<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/en/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/en/advanced/plugin.html" class="menu-link">Plugin Development</a></li><li><a href="/en/advanced/framework.html" class="menu-link">Framework</a></li><li><a href="/en/advanced/cluster-client.html" class="menu-link">Cluster Enhancement</a></li><li><a href="/en/advanced/view-plugin.html" class="menu-link">View Plugin</a></li><li><a href="/en/style-guide.html" class="menu-link">Style Guide</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">Community<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/en/plugins/" class="menu-link">Plugin List</a></li><li><a href="/en/contributing.html" class="menu-link">Contributing</a></li><li><a href="/en/resource.html" class="menu-link">Resource</a></li><li><a href="/en/faq.html" class="menu-link">FAQ</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
